#coding: utf-8

# Copyright (c) P SOFTHOUSE. All rights reserved.
import struct
from collections import defaultdict
from PencilQtBoilerplate import *
from PSOFT.vendor.Qt_py.Qt import QtCore, QtGui, QtWidgets
from PencilLineGuiUtils import getLocalizedString

import maya.cmds as cmds  # pylint: disable=E0401


class InstanceHolderType(type):
    __instances = defaultdict(list)

    def __init__(cls, name, bases, dct):
        super(InstanceHolderType, cls).__init__(name, bases, dct)

    def __call__(cls, *args, **kwargs):
        cls.__instances[cls].append(
            super(InstanceHolderType, cls).__call__(*args, **kwargs))
        return cls.__instances[cls][-1]

    @classmethod
    def instances(cls, cls_):
        return cls.__instances[cls_]

    @classmethod
    def removeInstance(cls, instance):
        if instance in cls.__instances[instance.__class__]:
            cls.__instances[instance.__class__].remove(instance)


class metaInstanceHolder(InstanceHolderType, type(QtWidgets.QWidget)):
    pass


# プレビューの配置、表示を行うクラス
class PencilBrushDetailPreview(QtWidgets.QWidget):
    __metaclass__ = metaInstanceHolder

    # BrushSettingsノードを保持する
    __brushSettingsNode = ""

    # インスタンスを取得する
    @classmethod
    def getInstances(cls):
        # cls.updateInstances()
        # return cls.__instances
        return cls.__metaclass__.instances(cls)

    # インスタンスを除去する
    def remove(self):
        self.__metaclass__.removeInstance(self)

    # 名前ラベルのコントロールの探索及び取得
    def searchNameFieldCtrl(self, ctrl):
        if ctrl == "":
            return ""

        # 階層を上りながら子をすべて探索する
        before = ctrl
        while True:
            parent = cmds.control(before, query=True, parent=True)
            # 親階層がなければ終了
            if not parent:
                return ""
            # With Maya 2016, for the Hypershade UI, Maya returns a parent that does not exist
            # and causes an error!!!
            # Check if the parent actually exists.
            if not cmds.control(parent, query=True, exists=True):
                return ""
            # 子をすべて探索する
            childCtrl = self.searchChildNameFieldCtrl(parent, before)
            if childCtrl != "":
                return childCtrl
            # 現在の親階層を保持
            before = parent
        return ""

    # 子を探索する
    def searchChildNameFieldCtrl(self, ctrl, noSearchCtrl):
        # 該当のNameFieldが見つかったらそのコントロール名を返す
        if "AEnodeNameField" in ctrl or "tearOffNameField" in ctrl:
            return ctrl
        # レイアウト以外を除外
        if not cmds.layout(ctrl, query=True, exists=True):
            return ""
        # さらに子を探索する
        cnum = cmds.layout(ctrl, query=True, numberOfChildren=True)
        children = cmds.layout(ctrl, query=True, childArray=True)
        for i in range(cnum):
            child = ctrl + '|' + children[i]
            # 探索対象外を除外
            if child in noSearchCtrl:
                continue
            # 探索
            childCtrl = self.searchChildNameFieldCtrl(child, "")
            # 値があればその値を返す
            if childCtrl != "":
                return childCtrl
        # 見つからない
        return ""

    # コンストラクタ
    def __init__(self, parent=None, control=None):
        super(PencilBrushDetailPreview, self).__init__(parent)

        # コントロールを保持する
        self.control = control

        # 存在するNameFieldを取得する
        #
        self.nameFieldControl = self.searchNameFieldCtrl(control)

        # 存在しなければプレビューを追加しない
        if not self.nameFieldControl:
            return

        # ラベル
        self.labelWidget = QtWidgets.QLabel(self)
        label = getLocalizedString("kBrushPreview")
        self.labelWidget.setText(label)
        # ペン
        self.brushTexture = QtGui.QPixmap()
        self.brushWidget = QtWidgets.QLabel(self)
        # ストローク
        self.strokeTexture = QtGui.QPixmap()
        self.strokeWidget = QtWidgets.QLabel(self)
        # レイアウト設定
        self.layout = QtWidgets.QHBoxLayout(self)
        self.layout.setContentsMargins(108,0,0,0)
        self.layout.setSpacing(0)
        self.setLayout(self.layout)
        self.layout.addWidget(self.labelWidget)
        self.layout.addSpacing(10)
        self.layout.addWidget(self.brushWidget)
        self.layout.addSpacing(15)
        self.layout.addWidget(self.strokeWidget)
        self.layout.addStretch(1)

        self.delayUpdateStack = {}

    def delayUpdate(self):
        for k, v in self.delayUpdateStack.items():
            v(k)
        self.delayUpdateStack = {}

    # 表示の更新
    def updateDisplay(self, node):

        # すでに存在していない場合はインスタンスリストから取り除く
        if not cmds.control(self.control, query=True, exists=True):
            self.remove()
            return

        if not self.nameFieldControl:
            self.remove()
            return

        if not cmds.control(self.nameFieldControl, query=True, exists=True):
            self.remove()
            return

        nameField = cmds.nameField(self.nameFieldControl, query=True, object=True)
        if nameField:
            # nameFieldと対象のノード
            if nameField != node:
                return
            # BrushDetailが開いていなければ更新しない
            if cmds.nodeType(nameField) != "PencilBrushDetail":
                return

        # 対象のBrushDetailノードが無ければ更新しない
        if not cmds.objExists(node):
            return

        # Qtウィジェットが無い場合も更新しない
        if not self.brushWidget or not self.strokeWidget:
            return

        # BrushSettingsノードの取得
        snode = ""
        dests = cmds.listConnections(node, s=True, t="PencilBrushSettings")
        if dests:
            if len(dests) >= 1:
                if self.__brushSettingsNode in dests:
                    snode = self.__brushSettingsNode
                else:
                    snode = dests[0]

        # 基本イメージサイズ
        siz = 64
        try:
            rsv = cmds.mayaDpiSetting(query=True, realScaleValue=True)
            siz *= rsv
        except AttributeError:
            pass

        # バッファの更新
        brushBuffer = cmds.pcl4detailpreview(dn=node, sn=snode, w=siz, h=siz, bbd=True)
        strokeBuffer = cmds.pcl4detailpreview(dn=node, sn=snode, w=siz*3, h=siz, sbd=True)
        # ブラシ
        img = self.getBufferToImage(brushBuffer, siz, siz)
        self.brushTexture.convertFromImage(img)
        self.brushWidget.setPixmap(self.brushTexture)
        # ストローク
        img = self.getBufferToImage(strokeBuffer, siz*3, siz)
        self.strokeTexture.convertFromImage(img)
        self.strokeWidget.setPixmap(self.strokeTexture)

    # バッファをイメージに変換して返す
    def getBufferToImage(self, buf, width, height):
        img = QtGui.QImage(width, height, QtGui.QImage.Format_RGB32)
        self.updateImage(img, buf)
        return img

    # イメージの更新
    def updateImage(self, image, buf):
        w = image.width()
        h = image.height()
        size = w * h * 4
        # バッファのサイズが異なる場合はバッファを枠のみの表示に書き換える
        if len(buf) != size:
            buf = [128 for i in range(size)]
            for x in range(w):
                self.setMonoColorFrame(x, 0, w, buf, 0)
                self.setMonoColorFrame(x, h-1, w, buf, 0)
            for y in range(h):
                self.setMonoColorFrame(0, y, w, buf, 0)
                self.setMonoColorFrame(w - 1, y, w, buf, 0)
        # バッファをイメージに流す
        ucharptr = image.bits()
        color = QtGui.QColor()
        i = 0
        for y in range(h):
            for x in range(w):
                color.setRgb(buf[i + 1], buf[i + 2], buf[i + 3])
                ucharptr[i:i+4] = struct.pack('I', color.rgb())
                i += 4

    # モノクロカラーをセット
    def setMonoColorFrame(self, x, y, w, buf, monoI):
        buf[(x + w * y) * 4] = 255
        buf[(x + w * y) * 4 + 1] = monoI
        buf[(x + w * y) * 4 + 2] = monoI
        buf[(x + w * y) * 4 + 3] = monoI

    # 遅延表示
    def deferredUpdateDisplay(self, node):
        self.delayUpdateStack[node] = self.updateDisplay
        utils.executeDeferred(self.delayUpdate)

    # BrushSettingsノードなら保持する
    @classmethod
    def setCurrentBrushSettings(cls, node):
        typ = cmds.nodeType(node)
        if typ == "PencilBrushSettings":
            cls.__brushSettingsNode = node


#------------------------------------------------------------------
# レイアウトの取得
def getLayout(layoutName):
    mayaLayoutPtr = omui.MQtUtil.findLayout(layoutName)
    if (mayaLayoutPtr is not None):
        return wrapInstance(long(mayaLayoutPtr), QtWidgets.QWidget)
    return None


# 表示用ウィジェットの作成
def buildAEBrushDetailPreview(layoutName, node):
    mayaLayout = getLayout(layoutName)
    if (mayaLayout is None):
        return
    mayaLayoutObj = mayaLayout.findChild(QtWidgets.QBoxLayout)

    widget = PencilBrushDetailPreview(parent=mayaLayout, control=layoutName)
    widget.updateDisplay(node)
    mayaLayoutObj.insertWidget(0, widget)


# 表示用ウィジェットの更新
def updateAEBrushDetailPreview(node):
    widgets = PencilBrushDetailPreview.getInstances()
    for widget in widgets:
        if widget is not None:
            widget.deferredUpdateDisplay(node)


# BrushSettingsノードを設定する
def setCurrentBrushSettings(node):
    PencilBrushDetailPreview.setCurrentBrushSettings(node)
